package de.uni_saarland.coli.doclists.data.btree.nodes;

import java.io.Serializable;

import de.uni_saarland.coli.doclists.data.btree.BTree;
import de.uni_saarland.coli.doclists.data.btree.SplitData;
import de.uni_saarland.coli.doclists.index.filesystem.FilesystemHelper;

/**
 * 
 * @author stefan
 * 
 *         Bucket nodes contain the data and the corresponding keys.
 * 
 * @param <T>
 *            The type of data.
 */
public class BucketNode<T> extends BaseNode<T> implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = -8481573776840384562L;

	/**
	 * the values
	 */
	private int[] values;

	/**
	 * number of values
	 */
	private int valueCount;

	/**
	 * Constructor.
	 */
	public BucketNode() {
		this.initialize();
		this.values = new int[3];
		this.valueCount = 0;
	}

	/**
	 * Constructor
	 * 
	 * @param bytes
	 *            serialized data
	 */
	public BucketNode(byte[] bytes) {
		this.initialize();
		this.values = new int[3];
		this.valueCount = bytes[1];

		for (int i = 0; i < 3; i++) {
			this.childIds[i] = FilesystemHelper.getIntFromByteArray(bytes,
					2 + i * 4);
		}

		for (int i = 0; i < 3; i++) {
			this.values[i] = FilesystemHelper.getIntFromByteArray(bytes,
					14 + i * 4);
		}
	}

	@Override
	public boolean isBucket() {
		return true;
	}

	@Override
	public boolean isNotBucket() {
		return false;
	}

	@SuppressWarnings("unchecked")
	@Override
	public SplitData<T> split() {
		System.out.println("Split Bucket node");
		// create new left node
		/*
		 * BucketNode<T> left = (BucketNode<T>) BTree.getNodeFactory()
		 * .createBucketNode(); left.childIds[0] = this.childIds[0];
		 * left.childIds[1] = this.childIds[1]; left.values[0] = this.values[0];
		 * left.values[1] = this.values[1]; left.valueCount = 2;
		 */

		// create new right node
		BucketNode<T> right = (BucketNode<T>) BTree.getNodeFactory()
				.createBucketNode();
		right.childIds[0] = this.childIds[2];
		right.values[0] = this.values[2];
		right.valueCount = 1;

		// remove right node content from this

		this.childIds[2] = Integer.MAX_VALUE;
		this.values[2] = 0;
		this.valueCount = 2;

		SplitData<T> splitResult = new SplitData<T>();
		splitResult.setKey(this.childIds[1]);
		splitResult.setLeft(this);
		splitResult.setRight(right);

		this.updateDistListEntry();
		right.updateDistListEntry();
		
		return splitResult;
	}

	/**
	 * shifts the values one to the left.
	 * 
	 * @param idx
	 *            first shited index
	 */
	public void shiftValueLeft(int idx) {
		for (int i = 2; i > idx; i--) {
			this.values[i] = this.values[i - 1];
		}
	}

	@SuppressWarnings("unchecked")
	private void insertValue(int key, T value) {
		int idx = this.findLink(key);

		this.shiftKeysLeft(idx);
		this.childIds[idx] = key;
		this.shiftValueLeft(idx);
		this.values[idx] = BTree.getNodeFactory().addValue(value);
		this.valueCount++;
//		System.out.println("shift " + idx + " " + key + " = " + value);

		this.updateDistListEntry();
	}

	@Override
	public SplitData<T> addData(int key, T value) {
		System.err.println("Adding " + key + " : " + value + " to a " + this.getClass());
		if (!this.isFull()) {
			System.err.println("This Bucket Node is *not* full!");
			this.insertValue(key, value);
			return null;
		} else {
			System.err.println("This Bucket Node is full!");

			System.err.println("I split!");
			SplitData<T> splitResult = this.split();
			System.err.println("I splittet");
			System.err.println("Split Key: " + splitResult.getKey());
			System.err.println("Left split: " + splitResult.getLeft());
			System.err.println("Right split: " + splitResult.getRight());

			if (key > splitResult.getKey()) {
				System.err.println("Bigger key");
				((BucketNode<T>) splitResult.getRight())
						.insertValue(key, value);
			} else {
				System.err.println("Lesser or equal key");
				((BucketNode<T>) splitResult.getLeft()).insertValue(key, value);
			}
//			System.out.println("split bucket");
			((BucketNode<T>) splitResult.getRight()).updateDistListEntry();
			((BucketNode<T>) splitResult.getLeft()).updateDistListEntry();

			System.out.println("left");
			System.out.println(splitResult.getLeft());
			System.out.println("----");
//			
			System.out.println("right");
			System.out.println(splitResult.getRight());
			
			
			
			return splitResult;
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public T get(int key) {
		int idx = this.findLink(key);

		if (idx < this.valueCount && this.childIds[idx] == key)
			return (T) BTree.getNodeFactory().getValue(this.values[idx]);// this.values.get(idx);

		return null;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void toStingHelper(String delim, StringBuilder sb) {
		for (int i = 0; i < 3; i++) {
			if (this.childIds[i] != Integer.MAX_VALUE)
				sb.append(delim)
						.append(this.childIds[i])
						.append(" =>\n")
						.append(delim)
						.append(" " + this.values[i] +": ")
						.append(((T) BTree.getNodeFactory().getValue(
								this.values[i])).toString()).append("\n");
			// else
			// sb.append(delim).append(this.childIds[i]).append(" no value")
			// .append("\n");
		}
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		this.toStingHelper("", sb);
		return sb.toString();
	}

	@Override
	public byte[] toBytes() {
		byte[] bytes = new byte[26];

		bytes[0] = 1;
		bytes[1] = (byte) this.valueCount;

		for (int i = 0; i < 3; i++) {
			FilesystemHelper.addIntToByteArray(bytes, this.childIds[i],
					2 + i * 4);
		}

		for (int i = 0; i < 3; i++) {
			FilesystemHelper.addIntToByteArray(bytes, this.values[i],
					14 + i * 4);
		}

		return bytes;
	}

}
